package com.ydlclass.configuration;

import com.ydlclass.compress.Compress;
import com.ydlclass.core.URL;
import com.ydlclass.loadbalance.LoadBalancer;
import com.ydlclass.registry.Registry;
import com.ydlclass.registry.zk.ZookeeperRegistry;
import com.ydlclass.serialization.Serializer;
import com.ydlclass.spi.SpiLoader;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author it楠老师
 * @createTime 2023-03-09
 */
@Data
@Slf4j
public class YrpcConfiguration {

    private YrpcConfiguration() {
    }

    private Properties properties = new Properties();

    private LoadBalancer loadBalance;

    private Compress compress;
    private Map<Byte,Compress> compressCache = new ConcurrentHashMap<>();

    private Serializer serializer;
    private Map<Byte,Serializer> serializerCache = new ConcurrentHashMap<>();;

    private Registry registry = new ZookeeperRegistry(new URL("zookeeper://127.0.0.1:2181"));

    private int port;

    private int timeout = 5000;

    /**
     * 内部建造者类，用于创建配置上下文实例
     */
    public static class Builder {
        private LoadBalancer loadBalance;
        private Compress compress;
        private Serializer serializer;

        public Builder loadBalance(LoadBalancer loadBalance) {
            this.loadBalance = loadBalance;
            return this;
        }

        public Builder compress(Compress compress) {
            this.compress = compress;
            return this;
        }

        public Builder serializer(Serializer serializer) {
            this.serializer = serializer;
            return this;
        }

        public YrpcConfiguration build() {
            YrpcConfiguration yrpcConfiguration = new YrpcConfiguration();

            // 配置实例的创建
            // 1、通过spi加载默认选项
            loadFromServices(yrpcConfiguration);

            // 2、通过yrpc.xml加载配置选项，客户可以通过配置文件加载自定义配置
            loadFromXml(yrpcConfiguration);

            // 3、加载代码配置
            yrpcConfiguration.setSerializer(serializer);
            yrpcConfiguration.setCompress(compress);
            yrpcConfiguration.setLoadBalance(loadBalance);
            return yrpcConfiguration;
        }

        /**
         * 加载spi，
         * @return
         */
        private void loadFromServices(YrpcConfiguration yrpcConfiguration) {
            yrpcConfiguration.setLoadBalance(
                    SpiLoader.get(LoadBalancer.class,"random")
            );
            yrpcConfiguration.setCompress(
                    SpiLoader.get(Compress.class,"gzip")
            );
            yrpcConfiguration.setSerializer(
                    SpiLoader.get(Serializer.class,"jdk")
            );
        }

        /**
         * 通过xml配置文件构建配置实例
         * @param inputStream xml文件的输入流
         * @return yrpcConfiguration 返回配置实例
         */
        private YrpcConfiguration loadFromXml(InputStream inputStream,YrpcConfiguration yrpcConfiguration) {

            try {
                // 解析xml文件，并赋值给yrpcConfiguration
                // 创建Document对象
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                // 禁用DTD校验：可以通过调用setValidating(false)方法来禁用DTD校验。
                dbf.setValidating(false);
                // 禁用外部实体解析：可以通过调用setFeature(String name, boolean value)方法并将“http://apache.org/xml/features/nonvalidating/load-external-dtd”设置为“false”来禁用外部实体解析。
                dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document doc = db.parse(inputStream);

                // 创建XPath对象
                XPathFactory factory = XPathFactory.newInstance();
                XPath parser = factory.newXPath();

                // 解析压缩算法
                LoadBalancer loadBalance = parse(LoadBalancer.class,parser, doc, "/configuration/loadBalance");
                if(loadBalance != null) {
                    yrpcConfiguration.setLoadBalance(loadBalance);
                }

                Compress compress = parse(Compress.class,parser, doc, "/configuration/compress");
                if (compress != null){
                    yrpcConfiguration.setCompress(compress);
                }

                Serializer serializer = parse(Serializer.class,parser, doc, "/configuration/serializer");
                if(serializer != null){
                    yrpcConfiguration.setSerializer(serializer);
                }

            } catch (ParserConfigurationException  | SAXException | IOException e){
                e.printStackTrace();
                log.error("An error occurred while parsing the configuration file.",e);
            }
            return null;
        }

        private void loadFromXml(YrpcConfiguration yrpcConfiguration) {
            InputStream inputStream = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("yrpc.xml");
            if (inputStream == null) {
                if(log.isDebugEnabled()){
                    log.debug("--> Yrpc's configuration file was not found in the classPath," +
                            "You can specify a custom path,user method loadFromXml(InputStream inputStream)");
                }
                return;
            }
            loadFromXml(inputStream,yrpcConfiguration);
        }


        private <T> T parse(Class<T> t,XPath parser,Document doc,String expression){
            try {
                Node node = (Node) parser.evaluate(expression, doc, XPathConstants.NODE);
                if(node == null) return null;
                NamedNodeMap attributes = node.getAttributes();
                Node classNode = attributes.getNamedItem("class");
                if(classNode != null && !"".equals(classNode.getNodeValue())){
                    Class<T> loadClass = (Class<T>)Thread.currentThread()
                            .getContextClassLoader().loadClass(classNode.getNodeValue());
                    Constructor<T> constructor = loadClass.getConstructor();
                    return constructor.newInstance();
                }
            } catch (XPathExpressionException | ClassNotFoundException | NoSuchMethodException |
                     InstantiationException | IllegalAccessException | InvocationTargetException e){
                log.error("An exception occurs in xpath while parsing an expression.",e);
            }
            return null;
        }
    }

    @SneakyThrows
    public Compress getCompress(byte compressType){
        if(compressCache.size() == 0){
            SpiLoader<Compress> spiLoader = SpiLoader.getSpiLoader(Compress.class);
            Map<String, Class<?>> stringClassMap = spiLoader.get();
            for(Map.Entry<String,Class<?>> entry : stringClassMap.entrySet()){
                Compress compress = (Compress)entry.getValue().getConstructor().newInstance();
                compressCache.put(compress.getCode(),compress);
            }
        }

        compressCache.put(this.compress.getCode(),this.compress);
        return compressCache.get(compressType);
    }

    @SneakyThrows
    public Serializer getSerializer(byte serializerType){
        if(serializerCache.size() == 0){
            // 加载所有的序列化器，包括spi、xml、当前配置的
            // 1、spi
            SpiLoader<Serializer> spiLoader = SpiLoader.getSpiLoader(Serializer.class);
            Map<String, Class<?>> stringClassMap = spiLoader.get();
            for(Map.Entry<String,Class<?>> entry : stringClassMap.entrySet()){
                Serializer serializer = (Serializer)entry.getValue().getConstructor().newInstance();
                serializerCache.put(serializer.getCode(),serializer);
            }
            // 2、todo 配置文件

            // 3、当前
            serializerCache.put(this.serializer.getCode(),this.serializer);

        }
        return serializerCache.get(serializerType);
    }

}
